using System;

namespace PropertyExpression.Common
{
    public static class OptionalValue
    {
        public static IOptionalValue<TValue> Nothing<TValue>()
        {
            return NotExistOptionalValue<TValue>.Instance;
        }

        public static IOptionalValue<TValue> AsOptionalValue<TValue>(
            this TValue value)
        {
            return new ExistOptionalValue<TValue>(value);
        }

        public static IOptionalValue<TTarget> ProcessValue<TValue, TTarget>(
            this IOptionalValue<TValue> optionalValue,
            Func<TValue, TTarget> func)
        {
            return PerformanceFix<TValue>.ProcessValue(optionalValue, func);
        }

        public static IOptionalValue<TTarget> ProcessValue<TValue, TTarget>(
            this IOptionalValue<TValue> optionalValue,
            Func<TValue, IOptionalValue<TTarget>> func)
        {
            return PerformanceFix<TValue>.ProcessValue(optionalValue, func);
        }

        public static void Process<TValue>(
            this IOptionalValue<TValue> optionalValue, 
            Action<TValue> existAction, 
            Action notExistAction)
        {
            PerformanceFix<TValue>.Process(optionalValue, existAction, notExistAction);
        }

        public static void ProcessValue<TValue>(
            this IOptionalValue<TValue> optionalValue, 
            Action<TValue> existAction)
        {
            PerformanceFix<TValue>.ProcessValue(optionalValue, existAction);
        }

        public static IOptionalValue<TTarget> Select<TValue, TTarget>(
            this IOptionalValue<TValue> optionalValue,
            Func<TValue, TTarget> func)
        {
            return optionalValue.ProcessValue(func);
        }

        public static IOptionalValue<TTarget> SelectMany<TValue, TTarget>(
            this IOptionalValue<TValue> optionalValue,
            Func<TValue, IOptionalValue<TTarget>> func)
        {
            return optionalValue.ProcessValue(func);
        }

        public static IOptionalValue<T2> SelectMany<TValue, T1, T2>(
            this IOptionalValue<TValue> optionalValue,
            Func<TValue, IOptionalValue<T1>> func1,
            Func<TValue, T1, T2> func2)
        {
            return optionalValue.SelectMany(
                value => func1(value).Select(
                    value1 => func2(value, value1)
                         )
                );
        }

        public static bool HasValue<TValue>(
            this IOptionalValue<TValue> optionalValue)
        {
            return PerformanceFix<TValue>.HasValue(optionalValue);
        }

        public static TValue ValueOrEx<TValue>(
            this IOptionalValue<TValue> optionalValue)
        {
            return PerformanceFix<TValue>.ValueOrEx(optionalValue);
        }

        public static TValue ValueOrDefault<TValue>(
            this IOptionalValue<TValue> optionalValue)
        {
            return PerformanceFix<TValue>.ValueOrDefault(optionalValue);
        }

        public static IOptionalValue<TValue> ToOptionalValue<TValue>(
            this TValue value) where TValue : class
        {
            return value == null ? Nothing<TValue>() : value.AsOptionalValue();
        }

        private static class PerformanceFix<TValue>
        {
            public static IOptionalValue<TTarget> ProcessValue<TTarget>(
                IOptionalValue<TValue> optionalValue,
                Func<TValue, TTarget> func)
            {
                if (optionalValue.HasValue())
                {
                    return func(optionalValue.ValueOrEx()).AsOptionalValue();
                }
                else
                {
                    return Nothing<TTarget>();
                }
            }

            public static IOptionalValue<TTarget> ProcessValue<TTarget>(
                IOptionalValue<TValue> optionalValue,
                Func<TValue, IOptionalValue<TTarget>> func)
            {
                return optionalValue.Process(func, Nothing<TTarget>);
            }

            public static void Process(
                IOptionalValue<TValue> optionalValue,
                Action<TValue> existAction,
                Action notExistAction)
            {
                optionalValue.Process(
                    value =>
                    {
                        existAction(value);
                        return new { };
                    },
                    () =>
                    {
                        notExistAction();
                        return new { };
                    }
                    );
            }

            public static void ProcessValue(
                IOptionalValue<TValue> optionalValue,
                Action<TValue> existAction)
            {
                Process(optionalValue, existAction, () => { });
            }

            public static bool HasValue(
                IOptionalValue<TValue> optionalValue)
            {
                return optionalValue.Process(delegate { return true; }, () => false);
            }

            public static TValue ValueOrEx(
                IOptionalValue<TValue> optionalValue)
            {
                return optionalValue.Process(
                    value => value,
                    () =>
                        {
                            throw new InvalidOperationException(
                                string.Format("Optional value of '{0}' type has no value.", typeof (TValue)));
                        }
                    );
            }

            public static TValue ValueOrDefault(
                IOptionalValue<TValue> optionalValue)
            {
                return optionalValue.Process(value => value, () => default(TValue));
            }
        }

        private class NotExistOptionalValue<TValue> : IOptionalValue<TValue>
        {
            public static readonly IOptionalValue<TValue> Instance = new NotExistOptionalValue<TValue>();

            private NotExistOptionalValue()
            {
            }

            public TResult Process<TResult>(
                Func<TValue, TResult> existFunc, Func<TResult> notExistFunc)
            {
                return notExistFunc();
            }
        }

        private class ExistOptionalValue<TValue> : IOptionalValue<TValue>
        {
            private readonly TValue value;

            public ExistOptionalValue(TValue value)
            {
                this.value = value;
            }

            public TResult Process<TResult>(
                Func<TValue, TResult> existFunc, Func<TResult> notExistFunc)
            {
                return existFunc(value);
            }
        }
    }
}